function createElement(type, props, ...children) {
  let element = {
    type,
    props: {
      ...props,
      children: children.map((child) =>
        typeof child === "object" ? child : createTextElement(child)
      ),
    },
  };
  return element;
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);
  return dom;
}

const isEvent = (key) => key.startsWith("on");
const isProperty = (key) => key !== "children" && !isEvent(key);
const isNew = (prev, next) => (key) => prev[key] !== next[key];
const isGone = (prev, next) => (key) => !(key in next);
function updateDom(dom, prevProps, nextProps) {
  //Remove old or changed event listeners
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) => {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // Remove old properties
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => {
      dom[name] = "";
    });

  if (nextProps) {
    // Set new or changed properties
    Object.keys(nextProps)
      .filter(isProperty)
      .filter(isNew(prevProps, nextProps))
      .forEach((name) => {
        dom[name] = nextProps[name];
      });

    // Add event listeners
    Object.keys(nextProps)
      .filter(isEvent)
      .filter(isNew(prevProps, nextProps))
      .forEach((name) => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, nextProps[name]);
      });
  }
}

function commitRoot() {
  deletions.forEach(fiber => commitWork(fiber));
  commitWork(wipRoot.child);
  currentRoot = wipRoot; // 当然root
  wipRoot = null;
}

function commitWork(fiber) { // 递归调用
  if (!fiber) {
    return;
  }

  let domParentFiber = fiber.parent;
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent;
  }
  const domParent = domParentFiber.dom; // 拿到真实的根dom元素

  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom); // 组件第一次挂载时tag就是PLACEMENT，这里直接添加fiber到父dom上即可
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    commitDeletion(fiber, domParent);
  }

  commitWork(fiber.child); // 挂载当前fiber的子，如果有就挂
  commitWork(fiber.sibling);// 挂载当前fiber的兄弟，如有就挂
}

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom);
  } else {
    commitDeletion(fiber.child, domParent);
  }
}

/**
 * 
 * react
 * 
 * 调度器 调和器 提交
 */

function render(element, container) {
  console.log("render : ",element)
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

let nextUnitOfWork = null; // 下一个执行fiber对象
let currentRoot = null; // 已经构建完成的最新的fiber根节点
let wipRoot = null;// work in progress root 正在构建的fiber根节点
let deletions = null; // 构建fiber树时收集的需要删除的旧fiber
/**
 * (浏览器每渲染一帧) 并且 (浏览器有空), 就执行一次performUnitOfWork
 */
function workLoop(deadline) {
 
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 拿到下一个fiber，如果浏览器有空闲就执行
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot(); // 每一次提交root都会将wipRoot设置为null
    console.log("commitRoot")
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);// 约等于 workLoop()
/**
 * 关于return顺序:
 * 1: 优先返回child
 * 2: 没有chidl就返回sibling
 * 3: 既没有child, 也没有sibling, 
 *    则返fiber.parent.sibling
 *    或者fiber.parent.parent.sibling
 *    或者fiber.parent.parent.parent.sibling
 *    直到找到为止
 *    如果fiber.parent是rootFiber, rootFiber.parent不存在 => 循环结束
 */
function performUnitOfWork(fiber) {
  const isFunctionComponent = fiber.type instanceof Function;
  if (isFunctionComponent) {
    updateFunctionComponent(fiber);
  } else {
    updateHostComponent(fiber);
  }
  if (fiber.child) {
    return fiber.child; // 1: 优先返回child
  }
  let nextFiber = fiber;
  while (nextFiber) { // 直到找到根fiber，如果还没有，返回null，说明该commitWork了
    if (nextFiber.sibling) {
      return nextFiber.sibling; // 2: 没有chidl就返回sibling
    }
    nextFiber = nextFiber.parent; // 3: 既没有child, 也没有sibling, 就返回.parent.sibling或者.parent.parent.sibling
  }
  return undefined;
}

let wipFiber = null;// 正在构建的fiber节点
let hookIndex = null;

function updateFunctionComponent(fiber) {
  wipFiber = fiber; // 保存下当前正在处理的fiber，这样做的目的是可以任务中断再开始后可以快速恢复之前的状态
  hookIndex = 0;    // useState每执行一次, hookIndex+1
  wipFiber.hooks = []; // useState每执行一次, wipFiber.hooks.push(hook)
  const children = [fiber.type(fiber.props)]; // 如果fiber是函数组件，那就执行type方法，把props传过去，返回值又是一个jsx转createElement
  reconcileChildren(fiber, children); // 继续调用协调器来处理父fiber和子fiber之间的关系
}

/**
 * 函数是组件每一次都会调用这个hook，然后遍历actions有没有
 * 有就一次执行action然后将action的结果赋值给hook的state
 * 最后由
 */
function useState(initial) { // 初始值就是函数式组件里面定义的useState，这个也会重定向到这个函数
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  /**
   * hook.state 的值由 oldHook 决定
   * 
   * 组件更新因别人而起:
   *   oldHook.queue = [], 
   *   hook.state === oldHook.state, 
   *   状态保持不变
   *   setState未执行
   *    
   * 组件更新因自己而起:
   *    oldHook.queue不为空, 
   *    hook.state的值由oldHook.queue决定, 
   *    状态将会发生变化
   *    setState被执行
   */
  const hook = { 
    state: oldHook ? oldHook.state : initial, // wipFiber的最新值, wipFiber.alternate 存放着 wipFiber 需要的最新值
    queue: [],// 用于存放setState的回调函数, 这里存放着正在构建的fiber节点的最新值
  };

  const actions = oldHook ? oldHook.queue : [];
  // action 是 setState 的回调函数
  // wipFiber.alternate 存放着 wipFiber 需要的最新值
  actions.forEach((action) => {
    hook.state = action(hook.state);//????????
  });

  const setState = (action) => { 
    hook.queue.push(action); // 把所有的改变都放到队列里
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      /**
       * 在每一个 fiber 节点上添加 alternate 属性用于记录旧 fiber 节点
       *（上一个 commit 阶段使用的 fiber 节点）的引用。
       */
      alternate: currentRoot,
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };

  wipFiber.hooks.push(hook);
  hookIndex++;
  return [hook.state, setState]; // 把state和setState函数暴露出去
}

/** 更新原生html标签 */
function updateHostComponent(fiber) {
  if (!fiber.dom) { // 判断当前fiber对象的dom是不是有，如果第一次fiber的dom指向的就是div#root
    fiber.dom = createDom(fiber);
  }
  reconcileChildren( // 如果fiber的dom有东西，就调用协调器处理该dom的子节点，当前例子子节点是一个函数（组件）
    fiber,
    (fiber.props && fiber.props.children) ? fiber.props.children : []
  );
}
/**
 * 通过虚拟dom elements 生成 子fiber
 * 建立子fiber和父fiber(wipFiber)的关系
 */
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;

  while (index < elements.length || oldFiber != null) { // 如果
    const element = elements[index];
    let newFiber = null;
    // 这里是简单diff, 就是从左往右无脑对比, 没有key
    const sameType = oldFiber && element && element.type == oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE", // 如果更新的子dom的类型和oldFiber的一样，那就是触发了组件更新
      };
    }
    if (element && !sameType) {
      newFiber = { // 如果第一次创建也就是mount的话，sameType肯定null，必然构建一个PLACEMENT的tag，而不是UPDATE或其他
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT",
      };
    }
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION";
      deletions.push(oldFiber); // 
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

    if (index === 0) {
      wipFiber.child = newFiber; // 所有的element都会被包装成fiber，然后wipFiber的child指向当前newFiber
    } else if (element) {
      prevSibling.sibling = newFiber;
    }

    prevSibling = newFiber;
    index++;
  }
}

const Didact = {
  createElement,
  render,
  useState,
};

/** 
 * 这个一定要使用低版本的react-scripts，因为其带的babel依赖版本较低，
 * 新版本的babel编译模式是自动的，不会走我们自己创建的 
 * https://zh-hans.legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
 */

/**@jsx Didact.createElement*/
function Counter() {
  const [state, setState] = Didact.useState(1);
  return (
    <h1 onClick={() => setState((c) => c + 1)} style="user-select: none">
      Count: {state}
    </h1>
  );
}
const element = <Counter />;
const container = document.getElementById("root");
Didact.render(element, container);
